#include "../Header/Precompiled.h"
#include "../Header/Graphics.h"
#include "../Header/GameObject.h"
#include "../Header/Vector2D.h"


	float gWinMaxX, gWinMinX, gWinMaxY, gWinMinY;
	bool gDebugDrawEnabled;
	int spritecounter = 0;
	Graphics::Graphics(HWND hWnd, int clientWidth, int clientHeight)
		:hWnd(hWnd),ScreenWidth(clientWidth),ScreenHeight(clientHeight)
	{
		pD3D = NULL;
		pDevice = NULL;
		pSpriteBuffer = NULL;
		pBackgroundBuffer = NULL;
		pParticleBuffer = NULL;

		gDebugDrawEnabled = false;



		//pCamera = NULL;

		InitD3D();
	}

	void Graphics::InitD3D()
	{
		//Create the D3D object (the parameter is there to make sure the app was built correctly).
		pD3D = Direct3DCreate9(D3D_SDK_VERSION);
		ErrorIf(!pD3D, "Couldn't get interface to Direct3D!");

		D3DDISPLAYMODE displayMode;
		pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &displayMode);

		//Set up the structure used to create the D3DDevice.
		ZeroMemory(&PresentParams, sizeof(PresentParams));

		D3DPRESENT_PARAMETERS& pp = PresentParams;
		pp.Windowed = true;						// Windowed or full-screen? NOTE: You can't just set this to FALSE--you'll need to change other stuff as well.
		pp.SwapEffect = D3DSWAPEFFECT_DISCARD;	// Picks the best way to handle back buffers for you, but it means you have draw a full screen every time.
		pp.BackBufferFormat = D3DFMT_UNKNOWN;		// This is for windowed apps, full screen will need to be explicit.
		pp.PresentationInterval  = D3DPRESENT_INTERVAL_DEFAULT;
		pp.BackBufferWidth = ScreenWidth;
		pp.BackBufferHeight = ScreenHeight;

		//Create the D3DDevice
    // IF THIS CALL FAILS:
    //   The most common cause is you are trying to run this on a machine that
    //   does not support hardware vertex processing. At DigiPen, this probably means
    //   your machine does not have a graphics card. Get one, switch machines, or 
    //   alternatively replace the D3DCREATE_HARDWARE_VERTEXPROCESSING constant below 
    //   with D3DCREATE_SOFTWARE_VERTEXPROCESSING.
		DXVerify(pD3D->CreateDevice(D3DADAPTER_DEFAULT,	// graphics adapter to be used.
									  D3DDEVTYPE_HAL,		// graphics device type: Hardware acceleration or software
									  hWnd,							// window Handle for the device.
										// device behavior: vertex processing (software, mixed, hardware), double precision, etc.
									  D3DCREATE_HARDWARE_VERTEXPROCESSING,	
									  &pp,						// presentation parameters created above
 									  &pDevice));			// pointer to the new device

		
		// Turn off lighting and culling.
		pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);	//Turn off culling
		pDevice->SetRenderState(D3DRS_LIGHTING, FALSE);					//Turn off D3D lighting

		// We need the following states in order to ensure that textures are transparent where they should be.
		pDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);					// Turn ON alpha blending.
		pDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);			// Use the texture's alpha channel.
		pDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);	// Use the inverse of the texture's alpha channel.

		SurfaceSize = Vector2D((float)ScreenWidth, (float)ScreenHeight);
	}

	// Load all graphical assets
	void Graphics::Load()
	{
		//Create the vertex buffer
		InitGeometry();

		LoadAssets();

		//if(!pCamera) CameraPos = Vector3D(0.0f,0.0f,-10.0f);

		SetupMatrices();
	}

	void Graphics::ReceiveMessage(Message* msg)
	{
		
	}

	// Clear backbuffer, draw
	void Graphics::Update(float frameTime)
	{

		//Clear the backbuffer and fill it with the background color.
		pDevice->Clear(
			0, // number of rectangles you are going to clear--0 means clear the whole thing 
			NULL, // array of rectangle to clear--just set it to NULL
			D3DCLEAR_TARGET, // what to clear (in this case, the render target, but could be a zbuffer or stencil buffer)
			D3DCOLOR_XRGB(0,0,0), // color to fill the target with
			1.0f, // value to fill the zbuffer with 
			0L); // value to fill stencil buffer with
		
		// Begin scene
		if(SUCCEEDED(pDevice->BeginScene()))
		{
			Render(frameTime);

			pDevice->EndScene();
		}

		pDevice->Present(NULL,NULL,NULL,NULL);
	}

	void Graphics::Render(float frameTime)
	{
		// Set up view and projection matrices for the world
		SetupMatrices();

		pDevice->SetFVF(TEXTURED_FVF);
		pDevice->SetStreamSource(0, pSpriteBuffer, 0, sizeof(TexturedVertex));
		DrawBackground();
		// Draw all object instances
		for (SpriteMap::iterator it = Sprites.begin(); it != Sprites.end(); ++it)
		{
			Sprite* pSprite = it->second;

			// skip dead objects
			//if (!(pSprite->GetOwner()->IsAlive()))
			//	continue;

			pSprite->Update(this,frameTime);
		}
	}

	void Graphics::Unload()
	{
		//delete Sprites[0];

		Textures.clear();
		//pCamera = NULL;
	}

	Graphics::~Graphics()
	{
		if(pSpriteBuffer) pSpriteBuffer->Release();
		pSpriteBuffer = NULL;

		if(pDevice) pDevice->Release();
		pDevice = NULL;

		if(pD3D) pD3D->Release();
		pD3D = NULL;
	}

	void Graphics::InitGeometry()
	{
		// Create the vertex buffer for sprites
		HRESULT hr = pDevice->CreateVertexBuffer(
			4*sizeof(TexturedVertex),	// size of the vertex buffer in bytes
			D3DUSAGE_WRITEONLY,				// gives usage hints to Direct3D
			TEXTURED_FVF,			// flexible vertex format for this buffer.
			D3DPOOL_MANAGED,	// where the buffer will be placed in memory
			&pSpriteBuffer,		// new vertex buffer.
			NULL);						// unused, reserved parameter.
		ErrorIf(FAILED(hr), "ERROR: Couldn't create sprite vertex buffer!");

		TexturedVertex* pVertices;
		// Lock the vertex buffer so that we can set the vertices.
		hr = pSpriteBuffer->Lock(
			0,		// offset in to the buffer to lock
			0,		// amount to lock (0 --> lock the whole buffer)
			(void**)&pVertices, // pointer to the actual vertex data
			0);	// type of lock (discard all data, read only, etc.)
		ErrorIf(FAILED(hr),	"ERROR: Couldn't lock vertex buffer!");

		// Set vertices to create a "quad" -- essentially a square composed
		// of 2 triangles (so make sure the vertices below reflect this).
		// The texture coordinates will be 0,0 1,0, 1,1, and 0,1,
		// corresponding to the 4 corners of a rectangular 2D texture.

		// NOTE: The position and texture coordinates appear to be
		// upside down in relation to each other. I believe this is due 
		// to the fact that images are typically stored upside down in 
		// memory relative to how they are viewed in an editor, although
		// I could be wrong.
		pVertices[0].Position = Vector3D(-0.5f, -0.5f, 0.0f);
		pVertices[0].tu = 0.0f; pVertices[0].tv = 1.0f;

		pVertices[1].Position = Vector3D(-0.5f, 0.5f, 0.0f);
		pVertices[1].tu = 0.0f; pVertices[1].tv = 0.0f;

		pVertices[2].Position = Vector3D(0.5f, -0.5f, 0.0f);
		pVertices[2].tu = 1.0f; pVertices[2].tv = 1.0f;

		pVertices[3].Position = Vector3D(0.5f, 0.5f, 0.0f);
		pVertices[3].tu = 1.0f; pVertices[3].tv = 0.0f;

		// Unlock the vertex buffer so we can use it for drawing.
		pSpriteBuffer->Unlock();

		// Create the vertex buffer for background textures
		hr = pDevice->CreateVertexBuffer(
			4*sizeof(TexturedVertex),	// size of the vertex buffer in bytes
			D3DUSAGE_WRITEONLY,				// gives usage hints to Direct3D
			TEXTURED_FVF,			// flexible vertex format for this buffer.
			D3DPOOL_MANAGED,			// where the buffer will be placed in memory
			&pBackgroundBuffer,		// new vertex buffer.
			NULL);							// unused, reserved parameter.
		ErrorIf(FAILED(hr), "ERROR: Couldn't create background vertex buffer!");
	}

	void Graphics::LoadAssets()
	{
		///////////////////////////
		//        TEXTURES       //
		///////////////////////////
		LoadTexture("assets/lonk.png");
		LoadTexture("assets/obstacle.png");
		LoadTexture("assets/lttp_map29.png");
	}

	// Load a texture from a file
	void Graphics::LoadTexture(std::string filename)
	{
		HRESULT hr = D3DXCreateTextureFromFile(
			pDevice,			// Direct3D device
			filename.c_str(), // name of texture file
			&Textures[filename]);  // where to copy texture data
		ErrorIf(FAILED(hr),"ERROR: Couldn't load texture!");
	}

	// Setup view and projection matrices
	void Graphics::SetupMatrices()
	{
		/*if(pCamera)
		{
			//Set the position of the camera.
			CameraPos.x = pCamera->GetOwner()->Position.x;
			CameraPos.y = pCamera->GetOwner()->Position.y;
		}*/

		CameraPos.x = 0.f;
		CameraPos.y = 0.f;
		CameraPos.z = -10.f;

		//The eye point is the location of the viewer (center of the screen, 10 units away).
		Vector3D eyePoint(CameraPos.x, CameraPos.y, CameraPos.z);
		//The look-at point is where the viewer is looking (center of the screen).
		Vector3D lookAtPoint(CameraPos.x, CameraPos.y, 0.0f );
		//The up vector defines which way is up (the y-direction).
		Vector3D upVector( 0.0f, 1.0f, 0.0f );
		//Create a left-handed view matrix.
		Matrix4 matView;
		D3DXMatrixLookAtLH(&matView, &eyePoint, &lookAtPoint, &upVector);
		//Store the view matrix
		ViewMatrix = matView;
		pDevice->SetTransform(D3DTS_VIEW, &ViewMatrix);

		//Create an orthogonal left-handed projection matrix.
		//This will transform everything to the view port with no perspective.
		//The near and far clipping plains are still needed, but not as important.
		Matrix4 matProj;
		D3DXMatrixOrthoLH(&matProj, SurfaceSize.x, SurfaceSize.y, 1.0f, 100.0f);
		//Store the projection matrix;
		ProjMatrix = matProj;
		pDevice->SetTransform(D3DTS_PROJECTION, &ProjMatrix);

		//Store the view projection matrix
		ViewProjMatrix = ViewMatrix * ProjMatrix;

		// Calculate screen bounds in world coords
		Vector2D max = ScreenToWorld(SurfaceSize);
		Vector2D min = ScreenToWorld(Zero());

		gWinMaxX = max.x; gWinMinX = min.x;
		gWinMaxY = min.y; gWinMinY = max.y;
	}

	Vector2D Graphics::ScreenToWorld(Vector2D pos)
	{
		// Convert to normalized screen space 0 to 1
		pos.x /= SurfaceSize.x;
		pos.y /= SurfaceSize.y;

		// Convert to projected space -1 to 1
		pos *= 2;
		pos -= Vector2D(1,1);
		pos.y *= -1;

		// "Unproject" the point by applying the inverse
		// of the ViewProjection matrix.
		Matrix4 inverseViewProj = Matrix4Inverse(ViewProjMatrix);
		Vector4D worldSpacePos = Matrix4MultVec2(inverseViewProj,pos);
		return Vector2D(worldSpacePos.x,worldSpacePos.y);
	}
	Sprite * Graphics :: AddSprites(string pat)
	{
		Sprites[spritecounter] = new Sprite(pat);
		spritecounter++;
		return Sprites[spritecounter-1];
	}

	Sprite * Graphics :: UpdateSprites()
	{
		return NULL;
	}
	void Graphics::DrawBackground()
	{
		TexturedVertex* pVertices;

		// Draw the background texture
		pDevice->SetTexture(0,Textures["assets/lttp_map29.png"]);

		pDevice->SetFVF(TEXTURED_FVF); // Flexible Vertex Format constants
		// Bind the vertex buffer to the D3D device's data stream
		pDevice->SetStreamSource(0, pBackgroundBuffer, 0, sizeof(TexturedVertex));

		// Compute texture coords
		float u0 = 0.0f + CameraPos.x/SurfaceSize.x;
		float v0 = 1.0f - CameraPos.y/SurfaceSize.x;
		float u1 = u0;
		float v1 = 0.0f - CameraPos.y/SurfaceSize.x;
		float u2 = 1.0f + CameraPos.x/SurfaceSize.x;
		float v2 = v0;
		float u3 = u2;
		float v3 = v1;

		// Lock the vertex buffer so that we can set the vertices.
		HRESULT hr = pBackgroundBuffer->Lock(
			0,		// offset in to the buffer to lock
			0,		// amount to lock (0 --> lock the whole buffer)
			(void**)&pVertices, // pointer to the actual vertex data
			0);	// type of lock (discard all data, read only, etc.)
		ErrorIf(FAILED(hr), "ERROR: Couldn't lock vertex buffer!");

		// Set the vertices (see InitGeometry for more info)
		pVertices[0].Position = Vector3D(-0.5f, -0.5f, 0.0f);
		pVertices[0].tu = u0; pVertices[0].tv = v0;

		pVertices[1].Position = Vector3D(-0.5f, 0.5f, 0.0f);
		pVertices[1].tu = u1; pVertices[1].tv = v1;

		pVertices[2].Position = Vector3D(0.5f, -0.5f, 0.0f);
		pVertices[2].tu = u2; pVertices[2].tv = v2;

		pVertices[3].Position = Vector3D(0.5f, 0.5f, 0.0f);
		pVertices[3].tu = u3; pVertices[3].tv = v3;

		// Unlock the vertex buffer so we can use it for drawing.
		pSpriteBuffer->Unlock();

		Matrix4 transform = Matrix4Scale(SurfaceSize.x,SurfaceSize.x,1.0f);
		transform *= Matrix4Translate(CameraPos.x,CameraPos.y,0.0f);
		pDevice->SetTransform(D3DTS_WORLD,&transform);

		// Draw!
		pDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
	}